package com.zeta.gateway.filter;

import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.context.model.SaResponse;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotPermissionException;
import cn.dev33.satoken.exception.NotRoleException;
import cn.dev33.satoken.filter.SaFilterAuthStrategy;
import cn.dev33.satoken.jwt.StpLogicJwtForMixin;
import cn.dev33.satoken.jwt.StpLogicJwtForSimple;
import cn.dev33.satoken.jwt.StpLogicJwtForStateless;
import cn.dev33.satoken.reactor.filter.SaReactorFilter;
import cn.dev33.satoken.router.SaHttpMethod;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.stp.StpLogic;
import cn.dev33.satoken.stp.StpUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.zetaframework.base.enums.ErrorCodeEnum;
import org.zetaframework.base.result.ApiResult;
import org.zetaframework.core.properties.IgnoreProperties;
import org.zetaframework.core.properties.TokenProperties;
import org.zetaframework.core.utils.JSONUtil;

import java.util.List;

/**
 * Sa-Token 权限认证 过滤器
 * <p>
 * 说明：
 * <pre>
 * 网关只进行登录认证，不做权限认证。
 * 权限认证由各个微服务进行认证
 * </pre>
 * @author gcc
 */
@Configuration
@EnableConfigurationProperties({IgnoreProperties.class, TokenProperties.class})
public class SaTokenFilter {

    public static final Logger logger = LoggerFactory.getLogger(SaTokenFilter.class);

    private final IgnoreProperties ignoreProperties;
    private final TokenProperties tokenProperties;

    public SaTokenFilter(IgnoreProperties ignoreProperties, TokenProperties tokenProperties) {
        this.ignoreProperties = ignoreProperties;
        this.tokenProperties = tokenProperties;
    }


    /**
     * SaToken过滤器[前置函数]：在每次[认证函数]之前执行
     *
     * <pre>
     * 说明：
     * saToken拦截的接口的跨域配置
     * BeforeAuth 不受 includeList 与 excludeList 的限制，所有请求都会进入
     * </pre>
     */
    private final SaFilterAuthStrategy beforeAuth = r -> {
        // saToken跨域配置
        SaHolder.getResponse()
                .setHeader("Access-Control-Allow-Origin", "*")
                .setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH")
                .setHeader("Access-Control-Max-Age", "3600")
                .setHeader("Access-Control-Allow-Headers", "*")
                // 是否启用浏览器默认XSS防护： 0=禁用 | 1=启用 | 1; mode=block 启用, 并在检查到XSS攻击时，停止渲染页面
                .setHeader("X-XSS-Protection", "1; mode=block");

        // 如果是预检请求，则立即返回到前端
        SaRouter.match(SaHttpMethod.OPTIONS).back();
    };

    /**
     * 注册 [Sa-Token全局过滤器]
     */
    @Bean
    public SaReactorFilter getSaReactorFilter() {
        List<String> notMatchUrl = ignoreProperties.getNotMatchUrl();
        logger.debug("不拦截的地址：{}", notMatchUrl);
        return new SaReactorFilter()
                .addInclude("/**")
                .setExcludeList(notMatchUrl)
                .setBeforeAuth(beforeAuth)
                // 对所有拦截到的地址进行登录验证
                .setAuth(r -> SaRouter.match("/**").check(() -> {
                    // 校验是否登录
                    StpUtil.checkLogin();

                    // token续期
                    if (tokenProperties.getAutoRenew()) {
                        StpUtil.renewTimeout(tokenProperties.getExpireTime());
                    }
                }))
                .setError(this::returnFail);
    }

    /**
     * Sa-Token token风格配置
     * @return StpLogic
     */
    @Bean
    public StpLogic stpLogic() {
        StpLogic stpLogic;
        switch (tokenProperties.getType()) {
            case SIMPLE -> {
                logger.info("检测到sa-token采用了[jwt-simple模式]");
                stpLogic = new StpLogicJwtForSimple();
            }
            case MIXIN -> {
                logger.info("检测到sa-token采用了[jwt-mixin模式]");
                stpLogic = new StpLogicJwtForMixin();
            }
            case STATELESS -> {
                logger.info("检测到sa-token采用了[jwt-stateless模式]");
                stpLogic = new StpLogicJwtForStateless();
            }
            default -> {
                logger.info("检测到sa-token采用了default模式");
                stpLogic = new StpLogic(StpUtil.TYPE);
            }
        }
        return stpLogic;
    }

    /**
     * return 错误消息
     *
     * 注意：这里的异常不会被GlobalExceptionHandler(全局异常处理器)捕获处理
     * @param e
     */
    private String returnFail(Throwable e) {
        // 初始化错误码和错误信息
        int statusCode = HttpStatus.BAD_REQUEST.value();
        int code = ErrorCodeEnum.FAIL.getCode();
        String message = "";

        if (e instanceof NotLoginException) {
            // 处理NotLoginException异常的错误信息
            message = switch (((NotLoginException) e).getType()) {
                case NotLoginException.NOT_TOKEN -> NotLoginException.NOT_TOKEN_MESSAGE;
                case NotLoginException.INVALID_TOKEN -> NotLoginException.INVALID_TOKEN_MESSAGE;
                case NotLoginException.TOKEN_TIMEOUT -> NotLoginException.TOKEN_TIMEOUT_MESSAGE;
                case NotLoginException.BE_REPLACED -> NotLoginException.BE_REPLACED_MESSAGE;
                case NotLoginException.KICK_OUT -> NotLoginException.KICK_OUT_MESSAGE;
                case NotLoginException.TOKEN_FREEZE -> NotLoginException.TOKEN_FREEZE_MESSAGE;
                case NotLoginException.NO_PREFIX -> NotLoginException.NO_PREFIX_MESSAGE;
                default -> NotLoginException.DEFAULT_MESSAGE;
            };
            code = ErrorCodeEnum.UNAUTHORIZED.getCode();
            statusCode = HttpStatus.UNAUTHORIZED.value();
        } else if (e instanceof NotRoleException || e instanceof NotPermissionException) {
            // 处理NotRoleException和NotPermissionException异常的错误信息
            message = ErrorCodeEnum.FORBIDDEN.getMsg();
            code = ErrorCodeEnum.FORBIDDEN.getCode();
            statusCode = HttpStatus.FORBIDDEN.value();
        } else {
            // 处理其它异常的错误信息
            message = e.getMessage();
        }

        // 手动设置Content-Type为json格式，替换之前重写SaServletFilter.doFilter方法的写法
        SaResponse response = SaHolder.getResponse();
        response.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
        response.setStatus(statusCode);

        return JSONUtil.toJsonStr(new ApiResult<Boolean>(code, message));
    }

}
